/*
 *  linux/fs/super.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * super.c contains code to handle the super-block tables.
 */
#include "../include/linux/sched.h"
#include "../include/linux/kernel.h"
#include "../include/asm/system.h"
#include "../include/errno.h"
#include "../include/sys/stat.h"
//#include "../include/linux/fs.h"

int sync_dev(int dev);

void wait_for_keypress(void);

/* set_bit uses setb, as gas doesn't recognize setc */
#define set_bit(bitnr, addr) ({ \
register int __res ; \
__asm__("bt %2,%3;setb %%al":"=a" (__res):"a" (0),"r" (bitnr),"m" (*(addr))); \
__res; })

struct super_block super_block[NR_SUPER];
/* this is initialized in init/main.c */
int ROOT_DEV = 0;

static void lock_super(struct super_block *sb) {
    cli();
    while (sb->s_lock)
        sleep_on(&(sb->s_wait));
    sb->s_lock = 1;
    sti();
}

static void free_super(struct super_block *sb) {
    cli();
    sb->s_lock = 0;
    wake_up(&(sb->s_wait));
    sti();
}

static void wait_on_super(struct super_block *sb) {
    cli();
    while (sb->s_lock)
        sleep_on(&(sb->s_wait));
    sti();
}

struct super_block *get_super(int dev) {
    struct super_block *s;

    if (!dev)
        return NULL;
    s = 0 + super_block;
    while (s < NR_SUPER + super_block)
        if (s->s_dev == dev) {
            wait_on_super(s);
            if (s->s_dev == dev)
                return s;
            s = 0 + super_block;
        } else
            s++;
    return NULL;
}

void put_super(int dev) {
    struct super_block *sb;
    /* struct m_inode * inode;*/
    int i;

    if (dev == ROOT_DEV) {
        printk("root diskette changed: prepare for armageddon\n\r");
        return;
    }
    if (!(sb = get_super(dev)))
        return;
    if (sb->s_imount) {
        printk("Mounted disk changed - tssk, tssk\n\r");
        return;
    }
    lock_super(sb);
    sb->s_dev = 0;
    for (i = 0; i < I_MAP_SLOTS; i++)
        brelse(sb->s_imap[i]);
    for (i = 0; i < Z_MAP_SLOTS; i++)
        brelse(sb->s_zmap[i]);
    free_super(sb);
    return;
}

//读取指定设备的超级块。
//如果指定设备 dev 上的文件系统超级块已经在超级块表中，则直接返回该超级块项的指针。
// 否则就从设备 dev 上读取超级块到缓冲块中，并复制到超级块表中。并返回超级块指针。
static struct super_block *read_super(int dev) {
    struct super_block *s;
    struct buffer_head *bh;
    int i, block;
    //首先判断参数的有效性。如果没有指明设备，则返回空指针。
    if (!dev)
        return NULL;
    // 检查该设备是否可更换 过盘片（也即是否是软盘设备）。
    // 如果更换过盘，则高速缓冲区有关该设备的所有缓冲块均失效，需要进行失效处理，即释放原来加载的文件系统。
    check_disk_change(dev);
    //如果该设备的超级块己经在超级块表中，则直接返回该超级块的指针。
    if ((s = get_super(dev)))
        return s;
    //否则，首先在超级 块数组中找出一个空项(也即字段s_dev=0 的项)。如果数组己经占满则返回空指针。
    for (s = 0 + super_block;; s++) {
        if (s >= NR_SUPER + super_block)
            return NULL;
        if (!s->s_dev)
            break;
    }
    //在超级块数组中找到空项之后，就将该超级块项用于指定设备 dev 上的文件系统。
    // 于是对该超级块结构中的内存字段进行部分初始化处理。
    s->s_dev = dev;
    s->s_isup = NULL;
    s->s_imount = NULL;
    s->s_time = 0;
    s->s_rd_only = 0;
    s->s_dirt = 0;
    // 锁定该超级块，并从设备上读取超级块信息到 bh 指向的缓冲块中。
    lock_super(s);
    //从设备上读取超级块信息到 bh 指向的缓冲块中。超级块位于块设备的第2个逻辑块（1号块）中，（第1个是引导盘块，即使该设备只是文件系统，不用引导盘块，也需要空出引导盘块）。
    if (!(bh = bread(dev, 1))) {
        // 如果读超级块操作失败，则释放上面选定的超级块数组中的项（即置s_dev=0），并解锁该项，返回空指针退出。
        s->s_dev = 0;
        free_super(s);
        return NULL;
    }
    // 读取超级块成功，就将设备超级块信息从缓冲块数据区复制到超级块数组相应项结构中。
    *((struct d_super_block *) s) = *((struct d_super_block *) bh->b_data);
    // 释放存放读取信息的高速缓冲块。
    brelse(bh);
    //从设备 dev 上得到了文件系统的超级块，于是开始检查这个超级块的有效性并从设 备上读取i节点位图和逻辑块位图等信息。
    // 如果所读取的超级块的文件系统魔数字段不对， 说明设备上不是正确的文件系统，因此同上面一样，释放上面选定的超级块数组中的项，
    // 并解锁该项，返回空指针退出。对于该版 Linux 内核，只支持 MINIX 文件系统1.0版本，其魔数是 0x137f。
    if (s->s_magic != SUPER_MAGIC) {
        s->s_dev = 0;
        free_super(s);
        return NULL;
    }
    //下面开始读取设备上i节点位图和逻辑块位图数据。
    // 首先初始化内存超级块结构中位图空间。
    for (i = 0; i < I_MAP_SLOTS; i++)
        s->s_imap[i] = NULL;
    for (i = 0; i < Z_MAP_SLOTS; i++)
        s->s_zmap[i] = NULL;
    // i节点位图保存 在设备上2号块开始的逻辑块中，共占用 s_imap_blocks 个块。
    // 逻辑块位图 在i节点位图所在块的后续块中，共占用s_zmap_blocks 个块。
    block = 2;
    //然后从设备上读取 i节点位图和逻辑块位图信息，并存放在超级块对应字段中。
    for (i = 0; i < s->s_imap_blocks; i++)
        if ((s->s_imap[i] = bread(dev, block)))
            block++;
        else
            break;
    for (i = 0; i < s->s_zmap_blocks; i++)
        if ((s->s_zmap[i] = bread(dev, block)))
            block++;
        else
            break;

    // 如果读出的位图块数不等于位图应该占有的逻辑块数，说明文件系统位图信息有问题，超级块初始化失败。
    // 因此只能释放前面申请并占用的所有资源，即释放 i 节点位图和逻辑块位图 占用的高速缓冲块、释放上面选定的超级块数组项、解锁该超级块项，并返回空指针退出。
    if (block != 2 + s->s_imap_blocks + s->s_zmap_blocks) {
        for (i = 0; i < I_MAP_SLOTS; i++)
            brelse(s->s_imap[i]);
        for (i = 0; i < Z_MAP_SLOTS; i++)
            brelse(s->s_zmap[i]);
        s->s_dev = 0;
        free_super(s);
        return NULL;
    }
    //一切成功。另外，由于对于申请空闲i节点的函数来讲，如果设备上所有的i节点己经全被使用，则查找函数会返回0值。
    //因此0号i节点是不能用的，所以这里将位图中第1块的最低比特位设置为1，以防止文件系统分配0号i节点。
    //同样的道理，也将逻辑块位图的最低位设置为1。
    s->s_imap[0]->b_data[0] |= 1;
    s->s_zmap[0]->b_data[0] |= 1;
    // 最后函数解锁该超级块，并返回超级块指针。
    free_super(s);
    return s;
}

int sys_umount(char *dev_name) {
    struct m_inode *inode;
    struct super_block *sb;
    int dev;

    if (!(inode = namei(dev_name)))
        return -ENOENT;
    dev = inode->i_zone[0];
    if (!S_ISBLK(inode->i_mode)) {
        iput(inode);
        return -ENOTBLK;
    }
    iput(inode);
    if (dev == ROOT_DEV)
        return -EBUSY;
    if (!(sb = get_super(dev)) || !(sb->s_imount))
        return -ENOENT;
    if (!sb->s_imount->i_mount)
        printk("Mounted inode has i_mount=0\n");
    for (inode = inode_table + 0; inode < inode_table + NR_INODE; inode++)
        if (inode->i_dev == dev && inode->i_count)
            return -EBUSY;
    sb->s_imount->i_mount = 0;
    iput(sb->s_imount);
    sb->s_imount = NULL;
    iput(sb->s_isup);
    sb->s_isup = NULL;
    put_super(dev);
    sync_dev(dev);
    return 0;
}

int sys_mount(char *dev_name, char *dir_name, int rw_flag) {
    struct m_inode *dev_i, *dir_i;
    struct super_block *sb;
    int dev;

    if (!(dev_i = namei(dev_name)))
        return -ENOENT;
    dev = dev_i->i_zone[0];
    if (!S_ISBLK(dev_i->i_mode)) {
        iput(dev_i);
        return -EPERM;
    }
    iput(dev_i);
    if (!(dir_i = namei(dir_name)))
        return -ENOENT;
    if (dir_i->i_count != 1 || dir_i->i_num == ROOT_INO) {
        iput(dir_i);
        return -EBUSY;
    }
    if (!S_ISDIR(dir_i->i_mode)) {
        iput(dir_i);
        return -EPERM;
    }
    if (!(sb = read_super(dev))) {
        iput(dir_i);
        return -EBUSY;
    }
    if (sb->s_imount) {
        iput(dir_i);
        return -EBUSY;
    }
    if (dir_i->i_mount) {
        iput(dir_i);
        return -EPERM;
    }
    sb->s_imount = dir_i;
    dir_i->i_mount = 1;
    dir_i->i_dirt = 1;        /* NOTE! we don't iput(dir_i) */
    return 0;            /* we do that in umount */
}

void mount_root(void) {
    int i, free;
    struct super_block *p;
    struct m_inode *mi;
    //若磁盘i节点结构不是32字节，则出错停机。该判断用于防止修改代码时出现不一致情况。
    if (32 != sizeof(struct d_inode))
        panic("bad i-node size");

    //首先初始化文件表数组（共64项，即系统同时只能打开64 个文件）和超级块表。
    for (i = 0; i < NR_FILE; i++)
        //将所有文件结构中的引用计数设置为0（表示空闲）
        file_table[i].f_count = 0;
    // 如果根文件系统所在设备是软盘的话，就提示“插入根文件系统盘，并按回车键”，并等待按键。
    if (MAJOR(ROOT_DEV) == 2) {
        printk("Insert root floppy and press ENTER");
        wait_for_keypress();
    }
    //把每个超级块表中各项结构的设备字段初始化为0（也表示空闲）
    for (p = &super_block[0]; p < &super_block[NR_SUPER]; p++) {
        p->s_dev = 0;
        p->s_lock = 0;
        p->s_wait = NULL;
    }
    //从根设备上读取文件系统超级块，
    if (!(p = read_super(ROOT_DEV)))
        panic("Unable to mount root");
    //取得文件系统根目录的i节点（1号节点）在内存 i节点表中的指针,iget(）函数中i节点引用计数己被设置为1
    if (!(mi = iget(ROOT_DEV, ROOT_INO)))
        panic("Unable to read root i-node");
    //下面对 超级块和根i节点(也就是根路径的i节点)进行设置。

    //把根i节点引用次数递增3次。iget 函数中 i节点引用计数己被设置为1，再加3，共4次。
    mi->i_count += 3;    /* NOTE! it is logically used 4 times, not 1 */
    //置该超级块的 被安装文件系统i节点和被安装到i节点字段为该i节点。
    p->s_isup = p->s_imount = mi;
    //设置当前进程的当前工作目录和根目录 i节点。此时当前进程是1号进程（init 进程）
    current->pwd = mi;
    current->root = mi;

    //对根文件系统上的资源作统计工作。统计该设备上空闲块数和空闲i节点数。
    free = 0;
    // i= 超级块中的设备逻辑块总数。
    i = p->s_nzones;
    //根据逻辑块位图中相应比特位的占用情况统计出空闲块数
    while (--i >= 0)
        //这里宏函数 set_bit(只是在测试比特位，而非设置比特位。
        // ”i&8191”用于取得i节点号在当前位图块中对应的比特位偏移值。
        // ”i>>13〞 是将i除以8192，也即除一个磁盘块包含的比特位数。
        if (!set_bit(i & 8191, p->s_zmap[i >> 13]->b_data))
            free++;
    //显示过设备上空闲逻辑块数/逻辑块总数之后。
    printk("%d/%d free blocks\n\r", free, p->s_nzones);

    //统计设备上空闲 i节点数。
    free = 0;
    // i = 超级块中设备上i节点总数+1。加1是将0节点也统计进去。
    i = p->s_ninodes + 1;
    while (--i >= 0)
        // 根据i节点位图中相应比特位的占用情况计算出空闲 i节点数。
        if (!set_bit(i & 8191, p->s_imap[i >> 13]->b_data))
            free++;
    // 显示设备上可用空闲 i节点数和i节点总数。
    printk("%d/%d free inodes\n\r", free, p->s_ninodes);
}